#include "dwarf/internal.hpp"
#include <cstring>
using namespace std;

namespace dwarf
{
value::value(const unit *cu,
             DW_AT name, DW_FORM form, type typ, section_offset offset)
    : cu(cu), form(form), typ(typ), offset(offset)
{
    if (form == DW_FORM::indirect)
        resolve_indirect(name);
}

section_offset
value::get_section_offset() const
{
    return cu->get_section_offset() + offset;
}

taddr value::as_address() const
{
    if (form != DW_FORM::addr)
        throw value_type_mismatch("cannot read " + to_string(typ) + " as address");

    cursor cur(cu->data(), offset);
    return cur.address();
}

/**
 * @brief TODO 这些块可以包含各种类型的内容，包括引用。
 * 然而，当前的最小API对于调用者无法解析这些引用
 *
 */
const void *value::as_block(size_t *size_out) const
{

    cursor cur(cu->data(), offset);
    switch (form) {
    case DW_FORM::block1:
        *size_out = cur.fixed<uint8_t>();
        break;
    case DW_FORM::block2:
        *size_out = cur.fixed<uint16_t>();
        break;
    case DW_FORM::block4:
        *size_out = cur.fixed<uint32_t>();
        break;
    case DW_FORM::block:
    case DW_FORM::exprloc:
        *size_out = cur.uleb128();
        break;
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as block");
    }
    cur.ensure(*size_out);
    return cur.pos;
}

uint64_t
value::as_uconstant() const
{
    cursor cur(cu->data(), offset);
    switch (form) {
    case DW_FORM::data1:
        return cur.fixed<uint8_t>();
    case DW_FORM::data2:
        return cur.fixed<uint16_t>();
    case DW_FORM::data4:
        return cur.fixed<uint32_t>();
    case DW_FORM::data8:
        return cur.fixed<uint64_t>();
    case DW_FORM::udata:
        return cur.uleb128();
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as uconstant");
    }
}

int64_t
value::as_sconstant() const
{
    cursor cur(cu->data(), offset);
    switch (form) {
    case DW_FORM::data1:
        return cur.fixed<int8_t>();
    case DW_FORM::data2:
        return cur.fixed<int16_t>();
    case DW_FORM::data4:
        return cur.fixed<int32_t>();
    case DW_FORM::data8:
        return cur.fixed<int64_t>();
    case DW_FORM::sdata:
        return cur.sleb128();
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as sconstant");
    }
}

expr value::as_exprloc() const
{
    cursor cur(cu->data(), offset);
    size_t size;
    switch (form) {
    case DW_FORM::exprloc:
    case DW_FORM::block:
        size = cur.uleb128();
        break;
    case DW_FORM::block1:
        size = cur.fixed<uint8_t>();
        break;
    case DW_FORM::block2:
        size = cur.fixed<uint16_t>();
        break;
    case DW_FORM::block4:
        size = cur.fixed<uint32_t>();
        break;
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as exprloc");
    }
    return expr(cu, cur.get_section_offset(), size);
}

loclist
value::as_loclist() const
{
    cursor cur(cu->data(), offset);
    return loclist(cu, cur.get_section_offset());
}

bool value::as_flag() const
{
    switch (form) {
    case DW_FORM::flag: {
        cursor cur(cu->data(), offset);
        return cur.fixed<ubyte>() != 0;
    }
    case DW_FORM::flag_present:
        return true;
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as flag");
    }
}

rangelist value::as_rangelist() const
{
    section_offset off = as_sec_offset();

    // 编译单元可能没有基地址
    die cudie = cu->root();
    taddr cu_low_pc = cudie.has(DW_AT::low_pc) ? at_low_pc(cudie) : 0;
    auto sec = cu->get_dwarf().get_section(section_type::ranges);
    auto cusec = cu->data();
    return rangelist(sec, off, cusec->addr_size, cu_low_pc);
}

die value::as_reference() const
{
    section_offset off;
    // 使用cursor的部分会带来一些额外的开销
    cursor cur(cu->data(), offset);
    switch (form) {
    case DW_FORM::ref1:
        off = cur.fixed<ubyte>();
        break;
    case DW_FORM::ref2:
        off = cur.fixed<uhalf>();
        break;
    case DW_FORM::ref4:
        off = cur.fixed<uword>();
        break;
    case DW_FORM::ref8:
        off = cur.fixed<uint64_t>();
        break;
    case DW_FORM::ref_udata:
        off = cur.uleb128();
        break;

    case DW_FORM::ref_addr: {
        off = cur.offset();
        // 通过gcc生成ref_addr 较少，不建缓存
        const compilation_unit *base_cu = nullptr;
        for (auto &file_cu : cu->get_dwarf().compilation_units()) {
            if (file_cu.get_section_offset() > off)
                break;
            base_cu = &file_cu;
        }
        die d(base_cu);
        d.read(off - base_cu->get_section_offset());
        return d;
    }

    case DW_FORM::ref_sig8: {
        uint64_t sig = cur.fixed<uint64_t>();
        try {
            return cu->get_dwarf().get_type_unit(sig).type();
        } catch (std::out_of_range &e) {
            throw format_error("unknown type signature 0x" + to_hex(sig));
        }
    }

    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as reference");
    }

    die d(cu);
    d.read(off);
    return d;
}

void value::as_string(string &buf) const
{
    size_t size;
    const char *p = as_cstr(&size);
    buf.resize(size);
    memmove(&buf.front(), p, size);
}

string value::as_string() const
{
    size_t size;
    const char *s = as_cstr(&size);
    return string(s, size);
}

const char * value::as_cstr(size_t *size_out) const
{
    cursor cur(cu->data(), offset);
    switch (form) {
    case DW_FORM::string:
        return cur.cstr(size_out);
    case DW_FORM::strp: {
        section_offset off = cur.offset();
        cursor scur(cu->get_dwarf().get_section(section_type::str), off);
        return scur.cstr(size_out);
    }
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as string");
    }
}

section_offset value::as_sec_offset() const
{
    // 在DWARF 4之前，sec_offsets被编码为data4或者data8
    cursor cur(cu->data(), offset);
    switch (form) {
    case DW_FORM::data4:
        return cur.fixed<uint32_t>();
    case DW_FORM::data8:
        return cur.fixed<uint64_t>();
    case DW_FORM::sec_offset:
        return cur.offset();
    default:
        throw value_type_mismatch("cannot read " + to_string(typ) + " as sec_offset");
    }
}

void value::resolve_indirect(DW_AT name)
{
    if (form != DW_FORM::indirect)
        return;

    cursor c(cu->data(), offset);
    DW_FORM form;
    do {
        form = (DW_FORM)c.uleb128();
    } while (form == DW_FORM::indirect);
    typ = attribute_spec(name, form).type;
    offset = c.get_section_offset();
}

string to_string(const value &v)
{
    switch (v.get_type()) {
    case value::type::invalid:
        return "<invalid value type>";
    case value::type::address:
        return "0x" + to_hex(v.as_address());
    case value::type::block: {
        size_t size;
        const char *b = (const char *)v.as_block(&size);
        string res = ::to_string(size) + " byte block:";
        for (size_t pos = 0; pos < size; ++pos) {
            res += ' ';
            res += to_hex(b[pos]);
        }
        return res;
    }
    case value::type::constant:
        return "0x" + to_hex(v.as_uconstant());
    case value::type::uconstant:
        return ::to_string(v.as_uconstant());
    case value::type::sconstant:
        return ::to_string(v.as_sconstant());
    case value::type::exprloc:
        return "<exprloc>";
    case value::type::flag:
        return v.as_flag() ? "true" : "false";
    case value::type::line:
        return "<line 0x" + to_hex(v.as_sec_offset()) + ">";
    case value::type::loclist:
        return "<loclist 0x" + to_hex(v.as_sec_offset()) + ">";
    case value::type::mac:
        return "<mac 0x" + to_hex(v.as_sec_offset()) + ">";
    case value::type::rangelist:
        return "<rangelist 0x" + to_hex(v.as_sec_offset()) + ">";
    case value::type::reference: {
        die d = v.as_reference();
        auto tu = dynamic_cast<const type_unit *>(&d.get_unit());
        if (tu)
            return "<.debug_types+0x" + to_hex(d.get_section_offset()) + ">";
        return "<0x" + to_hex(d.get_section_offset()) + ">";
    }
    case value::type::string:
        return v.as_string();
    }
    return "<unexpected value type " + to_string(v.get_type()) + ">";
}
} // namespace dwarf